  /*********************************************
 * OPL 12.5.1 Model
 * Author: Yosef Moatti
 * Creation Date: July 18, 2013
 * 
 *
 *********************************************/

 /*********************************************
 *		DATA MODEL
 *********************************************/

/*********************************************
 *		Tuple definitions
 *********************************************/
/* Sets the relative approximation gap to 1.5% */  
 execute {
    cplex.epgap = 0.015;
 };
 
 /* OPL_INPUT_1: the weights for various optimization subgoals: */ 
 int REWARD_WEIGHT = ...;
 int PLACEMENT_WEIGHT = ...;
 int RELOCATION_WEIGHT = ...;
 int ENERGY_WEIGHT = ...;
 int CAPACITY_OVERFLOW_WEIGHT = ...;
 int RED_LINE_WEIGHT = ...;

 /* OPL_INPUT_2: the various arrays or matrixes dimensions */
 int nbHosts = ...; 	// index j - Number of host
 int nbVM = ...; 		// index i - Number of VMs
 int nbResources = ...; // index k - Number of resources
 int nbPieces = ...;      // the number of pieces in the loadCost piecewise linear function
 float currentLoadBalancingScore = ...; // the current load balancing score 
 float idealLoadBalancingScore = ...; // the ideal load balancing score (where all the hosts where at equal load) 

 int penaltyOpenHostOnSpot = ...;  // the fine to be paid when a host has to be used while it is currently shutdown.
                                   // the fine is a % of hostPowerOnEnergyConsumption 

 int CPU  = 1;         //resource index for CPU
 
 range HOSTS = 1..nbHosts;
 range VMs = 1..nbVM;
 range RESOURCEs = 1..nbResources; //CPU is resource number 1 and CORE_PERCENT is resource number 2
 range RESOURCESWithoutCPU = 2..nbResources;
 range PIECEs  = 1..nbPieces;  
 range PIECEsNoLAST  = 1..nbPieces-1;  

 /* OPL_INPUT_3 single dimensional arrays of int */
 int vsCpuDemand[VMs] = ...;  // estimated demand for CPU in each VM
 float relocationCost[VMs] = ...; // cost incurred when changing the placement of a specific VM 
 int fixed[VMs] = ...;  // Valued 1 if the VM has fixed placement
 float loadCostSlopes[PIECEs] = ...;  // the nbPieces cost slopes for the CPU load cost function
 float loadBreakpoints[PIECEsNoLAST] = ...; // the nbPieces-1 knee points for the CPU load cost function
 int loadBalancingActivated = ...; // Input value is either 1 (normal) or 0 in case total demand exceeds capacity, in which case we disable the load balancing mechanism

 /* OPL_INPUT_4 2 dimensional arrays of int / float */
 int previousPlacement[VMs][HOSTS] = ...; // Matrix of 0-1s representing the last placement
 int capacity[HOSTS][RESOURCEs] = ...;  // capacity of each host for each resource 
 int reservation[VMs][RESOURCEs] = ...;  // requested resource for each VM, if this minimum is not met the VM cannot be instantiated 
 
 float gradient = ...;  // the cloud total population ration gradient (that is total reservations divided by total hosts capacities, including powered off hosts)
 int numberOfRelocation = ...; // Max number of allowed relocations.
 int redLine = ...;     // the % red line over which we will attempt to limit the host load (in percents)
 float hostBaseEnergyConsumption[HOSTS] = ...; // the host energy consumption (for one time unit)
 float hostPowerOnEnergyConsumption[HOSTS] = ...; // the cost of powering off/on a given host
 int activeHosts[HOSTS] = ...;  // input vlaues :activeHosts[h] = 1 <==> host index h is powered on 
 
 
 // For OPL (uses Tuples): 
 
 /*********************************************
 *		Decision VARiables
 *********************************************/
 
  /* OPL_DVAR 1  Output variables */
 dvar int+ targetPlacement[VMs][HOSTS] in 0..1;  // output of the placement algorithm
 dvar int+ targetActiveHost[HOSTS] in 0..1; // output vlaues: activeHosts[h] = 1 <==> host index h is powered on 
 dvar float+ cpu_allocation[VMs][HOSTS];  //allocation only for CPU     

 /* OPL_DVAR 2 Intermediary variables */
 dvar int+ placedInTarget[VMs] in 0..1;    // 1 <==> VM_i is placed in targetPlacement
 dvar int+ vmContainedHost[HOSTS];         // Number of VMs hosted in a given host:
 dvar int+ emptyHost[HOSTS] in 0..1;       // 1 <==> Host is empty
 dvar float+ cpuUnbalanceCost;         // total cost of overloaded host costs
 dvar float+ cpu_allocationOnHost[HOSTS];  // total allocation of CPU on host   
 dvar float+ hostCpuLoad[HOSTS];           // the relative CPU load on hosts
 dvar int+ changeMatrix[VMs][HOSTS] in 0..1;  // 1 <==> if in position <j,i> there has been a change (i.e. there was a VM and now is not there anymore or there is a VM which was not there before
 dvar int+ vmRelocated[VMs] in 0..1;       // !=0 if VM i has been relocated 
 dvar float+ allocationReward;  // reward obtained from the target placement
 dvar float+ totalRelocationCost;  // total cost due to VM relocation
 dvar float+ totalOverCapacityCost;  // total overcapacity cost due
 dvar int+ overCapacityCost[HOSTS][RESOURCESWithoutCPU];  // Per Host and per Resource overCapacityCost:
 dvar float+ vmPlacementGlobalGoal;  //total VMs placed
 dvar int+ emptyHosts; 

 /*********************************************
 *		EXPRESSIONS
 *********************************************/
  /* OPL_EXP_1 Expression */
 // dexpr float sumRelocationCost    = sum(v in VMs) relocationCost[v];
 dexpr int previouslyPlaced[v in VMs] = sum(h in HOSTS) previousPlacement[v][h];  // Whether a given VM was placed in current (initial) placement: 

  /*********************************************
 *		OBJECTIVE FUNCTION
 *********************************************/
 
 maximize 	
		 	- REWARD_WEIGHT * (1.0 - allocationReward)  
 		 	- PLACEMENT_WEIGHT * (1.0 - vmPlacementGlobalGoal) 	
		  	- RELOCATION_WEIGHT * ( loadBalancingActivated * (cpuUnbalanceCost - idealLoadBalancingScore) + totalRelocationCost ) / (currentLoadBalancingScore - idealLoadBalancingScore)
 			- CAPACITY_OVERFLOW_WEIGHT * totalOverCapacityCost
		  	+ ENERGY_WEIGHT * emptyHosts 
  		    ;

 /*********************************************
 *		CONSTRAINTS
 *********************************************/
 
  subject to {

     	// constraint #4
    	// each VM is placed at most in one host. placed[v] is defined as a binary variable
    	forall(v in VMs)
      		singleHostConstraint: 
   	  		placedInTarget[v] == sum(h in HOSTS) targetPlacement[v][h]; 	  

    	// constraint #5
    	// cpu allocation cannot be greater than reservation or demand
    	forall(v in VMs, h in HOSTS)  
    	cpuAllocationConstraint2:
  			cpu_allocation[v][h] <= vsCpuDemand[v] * targetPlacement[v][h];

		// constraint #6
		// have vmContainedHost[h] reflect the exact number of VMs placed in h
    	forall(h in HOSTS)
	  	VMContainedConstraint:
			vmContainedHost[h] == sum(v in VMs) targetPlacement[v][h]; 

		// constraint #7
		// force emptyHost[h] to be 1.0 if h is empty, else 0.0
    	forall(h in HOSTS)
	  	EmptyHostConstraint:
	  		emptyHost[h] <= 1.0 - (vmContainedHost[h]/card(VMs));

		// constraint #8
		// Set emptyHosts to its value
    	forall(h in HOSTS)
	  	EmptyHostConstraint2:
	  		emptyHosts == sum(h in HOSTS) emptyHost[h];

    	// constraint #9
   	    // fixed VMs have imposed placement => fix the placement variable
    	forall(v in VMs, h in HOSTS)  
      		noChoiceConstraint:
       			targetPlacement[v][h] * fixed[v] == previousPlacement[v][h] * fixed[v] ;

    	// constraint #10
   	    // sets totalOverCapacityCost to the sum for all hosts and resources of overCapacityCost
	    totalOverCapacityCostConstraint:
    		totalOverCapacityCost == sum(h in HOSTS, k in RESOURCESWithoutCPU) overCapacityCost[h][k];

    	// constraint #11
   	    // forces overCapacityCost[h][k] which by definition is >= 0, to be bigger than the specific resource overflow on the host. 
   	    // This is relevant only when the overCapacity is positive. In this case, the optimization goal will cause overCapacityCost to be lowered to the exact over capacity. 
    	overCapacityCostConstraint:
    	forall(h in HOSTS, k in RESOURCESWithoutCPU)
    		overCapacityCost[h][k] >= ( sum(v in VMs) targetPlacement[v][h] * reservation[v][k]) - capacity[h][k];
          
    	// constraint #12
   	    // sets cpu_allocationOnHost[h] to the sum all VM cpu allocations
    	forall(h in HOSTS)
	  	allocationOnHostConstraint:
			cpu_allocationOnHost[h] ==  sum(v in VMs) cpu_allocation[v][h];

    	// constraint #14
   	    // constraints VM allocations so as to abide with host capacity and margins
    	forall(h in HOSTS)
        	capacityConstraintOnCPU:
        		cpu_allocationOnHost[h] <= capacity[h][CPU];        

    	// constraint #14B
    	forall(h in HOSTS)
        	hostCpuLoadConstraint:
        		hostCpuLoad[h] == cpu_allocationOnHost[h] / capacity[h][CPU];        

    	// constraint #15
   	    // Prevents VMs which have negative placement priorities to be placed
    	forall(v in VMs)
      		priorityConstraint:
        		placedInTarget[v] >= 0;   
      
    	// constraint #18
   	    // Define the change matrix
     	forall(h in HOSTS, v in VMs)
	    	changeMatrixConstraint:
			changeMatrix[v][h] == targetPlacement[v][h] + previousPlacement[v][h] - (2 * targetPlacement[v][h] * previousPlacement[v][h]);	

    	// constraint #19
   	    // Sets vmRelocated[v] to 1.0 if it was relocated, else it will be valued 0.0
     	forall(v in VMs)
		relocatedConstraint:
 			vmRelocated[v] >= ( sum(h in HOSTS)  changeMatrix[v][h] ) / 2.0;

    	// constraint #19B
    	// Limit the total number of relocations to numberOfRelocation 
 		(sum(v in VMs) vmRelocated[v]) <= numberOfRelocation;

    	// constraint #20
   	    // Sets the global allocationReward to be the sum for all VMs of the ratio cpu allocation / vsCpuDemand 
   	    // where the sum is weighed by the (normalized) vmShare of the VM.
   	    // allocationReward is valued between 0.0 and 1.0 
		allocationRewardConstraint:
    		allocationReward == (sum(v in VMs, h in HOSTS) (cpu_allocation[v][h] / vsCpuDemand[v]) );

    	// constraint #21
   	    // Sets the global totalRelocationCost to be the ratio of:
   	    // the sum for all relocation costs for the relocated VMs  
   	    // the maximal relocation cost (obtained if each VM was relocated)
   	    // totalRelocationCost is valued between 0.0 and 1.0 
		totalRelocationCostConstraint:
			totalRelocationCost == sum(v in VMs) previouslyPlaced[v] * vmRelocated[v] * relocationCost[v];

    	// constraint #25
   	    // force the global vmPlacementGlobalGoal to be sum of all placement (relative) priorities for the placed VMs  
	    totalPlacedConstraint:  
				vmPlacementGlobalGoal == (sum(v in VMs) placedInTarget[v]) / nbVM;
				
		// constraint #26
		// computes the global CPU load balancing cost as the sum of a piecewise linear function for each of the hosts	
	       loadBalancingConstraint2:
		      cpuUnbalanceCost == (sum(h in HOSTS)
	    		piecewise(i in PIECEsNoLAST) { 
		    	  loadCostSlopes[i] -> loadBreakpoints[i];
			      loadCostSlopes[nbPieces] 
			    }  hostCpuLoad[h]);


  }				
 